package com.example.util;

import org.springframework.stereotype.Service;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Service;

import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
/**
 * @ClassName Sm2Util
 * @Description TODO
 * @Author kun
 * @Date 2024-10-22 11:05
 * @Version 1.0
 */
@Service
@Slf4j
public class Sm2Util {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static String sm4Aly = "SM4/CBC/PKCS7Padding";
    public static String SM2_pubKey_encrypt_oid = "1.2.156.10197.1.301.3";
    public static String sm2Data_oid = "1.2.156.10197.6.1.4.2.1";
    public static String sm2EnvelopedData_oid = "1.2.156.10197.6.1.4.2.3";
    public static String sm4_CBC_oid = "1.2.156.10197.1.104";
    public static String SubjectKey_oid = "2.5.29.14";

    /**
     * 生成SM2密钥对
     *
     * @param isCompressed 是否压缩公钥
     * @return Sm2KeyPair
     */
    public static Sm2KeyPair generateKeyPair(boolean isCompressed) {
        SM2 sm2 = new SM2();
        sm2.init();
        String publicKey = Hex.toHexString(sm2.getQ(isCompressed));
        String privateKey = Hex.toHexString(sm2.getD());
        return new Sm2KeyPair(publicKey, privateKey);
    }

    private static String uncompressPublicKey(String publicKey) {
        if (publicKey.startsWith("04")) {
            return publicKey;
        }
        //还原公钥
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造ECC算法参数，曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        String uncompressedPublicKey = Hex.toHexString(publicKeyParameters.getQ().getEncoded(false));
        return uncompressedPublicKey;
    }

    /**
     * 加密数据
     *
     * @param publicKey
     * @param data
     * @return
     */
    public static String encrypt1(String publicKey, String data) {
        try {
            SM2 sm2 = SmUtil.sm2(null, publicKey);
            sm2.setMode(SM2Engine.Mode.C1C2C3);
            return sm2.encryptHex(data, KeyType.PublicKey);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    /**
     * 解密数据
     *
     * @param privateKey
     * @param data
     * @return
     */
    public static String decrypt1(String privateKey, String data) {
        try {
            SM2 sm2 = SmUtil.sm2(privateKey, null);
            sm2.setMode(SM2Engine.Mode.C1C2C3);
            return sm2.decryptStr(data, KeyType.PrivateKey);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }


    /**
     * x509证书加密 加密
     *
     * @param cert   公钥证书
     * @param source 待加密数据
     * @return 加密后数据
     */
    public static String envelopData(X509Certificate cert, String source) {
        try {
            //生成对称密钥key和IV
            KeyAndIV keyAndIV = generateKeyAndIV();
            //创建SM2Engine
            SM2Engine engine = new SM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
            ECPublicKeyParameters ecPublicKeyParameters = (ECPublicKeyParameters) ECUtil.generatePublicKeyParameter(cert.getPublicKey());
            ParametersWithRandom parametersWithRandom = new ParametersWithRandom(ecPublicKeyParameters);
            engine.init(true, parametersWithRandom);
            //使用公钥对对称密钥加密
            byte[] encryptedBytes = engine.processBlock(keyAndIV.getKey(), 0, keyAndIV.getKey().length);

            //构造sm2加密结果结构体
            ASN1OctetString sm2CipherObject = getSm2CipherObject(encryptedBytes, parametersWithRandom);

            //使用对称密钥对原文进行加密
            byte[] sm4EncryptCBCByte = getSm4Encrypt(source, keyAndIV);
            ASN1OctetString encryptedOctet = new BEROctetString(sm4EncryptCBCByte);

            EncryptedContentInfo encryptedContentInfo = new EncryptedContentInfo(new ASN1ObjectIdentifier(sm2Data_oid),
                    new AlgorithmIdentifier(new ASN1ObjectIdentifier(sm4_CBC_oid), new DEROctetString(keyAndIV.getIv())), encryptedOctet);
            //获取subjectKeyIdentifier 内容
            RecipientIdentifier recipientIdentifier = getRecipientIdentifier(cert);
            //构造接收者信封结构体
            ASN1EncodableVector recipientInfos = getAsn1EncodableVector(recipientIdentifier, sm2CipherObject);

            EnvelopedData envelopedData = new EnvelopedData(null, new DERSet(recipientInfos), encryptedContentInfo, ASN1Set.getInstance(null));
            ContentInfo contentInfo = new ContentInfo(new ASN1ObjectIdentifier(sm2EnvelopedData_oid), envelopedData);
            CMSEnvelopedData cmsEnvelopedData = new CMSEnvelopedData(contentInfo);
            return Base64.toBase64String(cmsEnvelopedData.getEncoded());
        } catch (InvalidKeyException | InvalidCipherTextException | CertificateEncodingException | IOException |
                 CMSException e) {
            e.printStackTrace();
            return "";
        }
    }

    private static ASN1EncodableVector getAsn1EncodableVector(RecipientIdentifier recipientIdentifier, ASN1OctetString sm2CipherObject) {
        KeyTransRecipientInfo keyTransRecipientInfo =
                new KeyTransRecipientInfo(recipientIdentifier,
                        new AlgorithmIdentifier(
                                new ASN1ObjectIdentifier(SM2_pubKey_encrypt_oid), DERNull.INSTANCE), sm2CipherObject);
        RecipientInfo receiverCerts = new RecipientInfo(keyTransRecipientInfo);
        ASN1EncodableVector recipientInfos = new ASN1EncodableVector();
        recipientInfos.add(receiverCerts);
        return recipientInfos;
    }

    /**
     * 从证书中解析subjectKeyIdentifier
     */
    private static RecipientIdentifier getRecipientIdentifier(X509Certificate cert) throws CertificateEncodingException, IOException {
        TBSCertificate instance = TBSCertificate.getInstance(cert.getTBSCertificate());
        Extensions extensions = instance.getExtensions();
        Extension extension = extensions.getExtension(new ASN1ObjectIdentifier(SubjectKey_oid));
        byte[] octets = extension.getExtnValue().getOctets();
        ASN1InputStream asn1InputStream = new ASN1InputStream(new ByteArrayInputStream(octets));
        ASN1Object asn1Object = asn1InputStream.readObject();
        SubjectKeyIdentifier subjectKeyIdentifier = SubjectKeyIdentifier.getInstance(asn1Object);
        DEROctetString derOctetString = new DEROctetString(subjectKeyIdentifier.getKeyIdentifier());
        return new RecipientIdentifier(derOctetString);
    }

    /**
     * 根据加密结果构建需要解密内容结构体
     */

    private static ASN1OctetString getSm2CipherObject(byte[] bytes, ParametersWithRandom parametersWithRandom) throws IOException {

        ECKeyParameters ecKey = (ECKeyParameters) parametersWithRandom.getParameters();
        ECDomainParameters ecParams = ecKey.getParameters();

        byte[] c1Bytes = new byte[65];
        System.arraycopy(bytes, 0, c1Bytes, 0, c1Bytes.length);
        byte[] c3Bytes = new byte[32];
        System.arraycopy(bytes, c1Bytes.length, c3Bytes, 0, c3Bytes.length);
        byte[] c2Bytes = new byte[bytes.length - 32 - 65];
        System.arraycopy(bytes, c1Bytes.length + c3Bytes.length, c2Bytes, 0, c2Bytes.length);
        ECPoint c1Pont = ecParams.getCurve().decodePoint(c1Bytes);
        BigInteger X = c1Pont.normalize().getAffineXCoord().toBigInteger();
        BigInteger Y = c1Pont.normalize().getAffineYCoord().toBigInteger();

        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(X));
        v.add(new ASN1Integer(Y));
        v.add(new DEROctetString(c3Bytes));
        v.add(new DEROctetString(c2Bytes));
        return new DEROctetString(new DERSequence(v));
    }

    /**
     * sm4对称算法加密原文
     */
    private static byte[] getSm4Encrypt(String source, KeyAndIV keyAndIV) {
        byte[] sm4EncryptCBCByte;
        try {
            Key keys = new SecretKeySpec(keyAndIV.getKey(), "SM4");

            Cipher out = Cipher.getInstance(sm4Aly, "BC");
            IvParameterSpec ivSpec = new IvParameterSpec(keyAndIV.getIv());
            out.init(Cipher.ENCRYPT_MODE, keys, ivSpec);
            sm4EncryptCBCByte = out.doFinal(source.getBytes());
        } catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException |
                 InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException(e);
        }
        return sm4EncryptCBCByte;
    }

    /**
     * 生成sm4加密key和初始向量
     */
    public static KeyAndIV generateKeyAndIV() {

        int ivLength = 16, keyLength = 16;
        byte[] buffer = new byte[ivLength + keyLength];
        SecureRandom random = new SecureRandom();
        random.nextBytes(buffer);
        byte[] key = new byte[keyLength];
        System.arraycopy(buffer, 0, key, 0, key.length);
        byte[] iv;
        iv = new byte[ivLength];
        System.arraycopy(buffer, key.length, iv, 0, iv.length);
        return new KeyAndIV(key, iv);
    }

    @Setter
    @Getter
    public static class KeyAndIV {
        private byte[] key;
        private byte[] iv;

        public KeyAndIV(byte[] key, byte[] iv) {
            this.key = key;
            this.iv = iv;
        }

    }

    /**
     * 私钥签名
     *
     * @param privateKey 私钥
     * @param content    待签名内容
     * @return
     */
    public static String sign(String privateKey, String content) {
        try {
            //待签名内容转为字节数组
            byte[] message = Hex.decode(content);

            //获取一条SM2曲线参数
            X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
            //构造domain参数
            ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
                    sm2ECParameters.getG(), sm2ECParameters.getN());

            BigInteger privateKeyD = new BigInteger(privateKey, 16);
            ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);

            //创建签名实例
            SM2Signer sm2Signer = new SM2Signer();

            //初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678
            try {
                sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray("1234567812345678")));
            } catch (NoSuchAlgorithmException e) {
                log.error("签名时出现异常:", e);
            }
            sm2Signer.update(message, 0, message.length);
            //生成签名,签名分为两部分r和s,分别对应索引0和1的数组
            byte[] signBytes = sm2Signer.generateSignature();

            String sign = Hex.toHexString(signBytes);

            return sign;
        }catch (Exception e){
            return "";
        }

    }

    /**
     * 验证签名
     *
     * @param publicKey 公钥
     * @param content   待签名内容
     * @param sign      签名值
     * @return
     */
    public static boolean verify(String publicKey, String content, String sign) {
        //待签名内容
        byte[] message = Hex.decode(content);
        byte[] signData = Hex.decode(sign);

        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
                sm2ECParameters.getG(),
                sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        //创建签名实例
        SM2Signer sm2Signer = new SM2Signer();
        ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters, Strings.toByteArray("1234567812345678"));
        sm2Signer.init(false, parametersWithID);
        sm2Signer.update(message, 0, message.length);
        //验证签名结果
        boolean verify = sm2Signer.verifySignature(signData);
        return verify;
    }

    public static void main(String[] args) {
//        Sm2KeyPair keyPair = Sm2Util.generateKeyPair(true);
//        System.out.println("公钥:" + keyPair.getPublicKey());
//        System.out.println("私钥:" + keyPair.getPrivateKey());
        String pubKey = "0230a768c7dae8b2d729a11806001e080607eed3047514f92d181b93e71e0d9728";
        String priKey = "ad9e1083c6f140b39a9dfcf96804fd58ff9120efad6ccbac9861a90beddb61f3";
        //明文
        String text = "{\"tid\":\"D6200005491ab9ef2ef058b454082c4bedb9a2af574\"}";
        System.out.println("测试明文文本:" + text);

        //签名验签测试
        String sign = "";
        String encryptData = encrypt1(pubKey, text);
        System.out.println("加密结果:" + encryptData);
        sign = sign(priKey, encryptData);
        System.out.println("生成签名:" + sign);
        boolean verify = verify(pubKey, encryptData, sign);
        System.out.println("验签结果:" + verify);

        String decryptData = decrypt1(priKey, encryptData);
        System.out.println("解密结果:" + decryptData);

    }
}
